#include "system/window.h"
#include "system/windoweventlistener.h"
#include "system/windoweventarg.h"

#include "system/windowsmanager.h"

namespace dw {
	namespace system {

//------------------------------------------------------------------------------
// Construction & destruction
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Window::Window(char* name, uint32 width, uint32 height, uint32 bitDepth /* = 32 */, bool fullScreen /* = false */)
:	_hInstance(::GetModuleHandle(NULL))
,   _hWnd(NULL)
,	_setup(0, 0, width, height, bitDepth, fullScreen)
,	_isActive(false)
{
	dwAssert(_hInstance != NULL, "Module handle is invalid");
	dwAssert(strlen(name) < NAME_MAX_LENGTH, "Window's name is too long");

	// _name
	strncpy_s(_name, name, NAME_MAX_LENGTH);
	_name[NAME_MAX_LENGTH - 1] = 0;

	// Class Name
	sprintf_s(_className, sizeof (_className), "%s%s", _name, CLASS_NAME_SUFFIX);
	_className[CLASS_NAME_MAX_LENGTH - 1] = 0;

	create();

	setTitle(_name);

	WindowsManager::getInstance().registerWindow(this);
}

//------------------------------------------------------------------------------
Window::~Window()
{
	dwAssert(_listeners.empty(), "Some EventListeners have not been unregistered");

	WindowsManager::getInstance().unregisterWindow(this);

	destroy();
}

//------------------------------------------------------------------------------
// EventListeners Commands
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void Window::registerListener(EventListener* listener)
{
	dwAssert(listener != NULL, "Invalid parameter: listener == NULL");
	dwAssert(std::find(_listeners.begin(), _listeners.end(), listener) == _listeners.end(), "Invalid parameter: listener is already registered");

	_listeners.push_back(listener);

	EventArg arg;

	listener->onRegistered(this, arg);
}

//------------------------------------------------------------------------------
void Window::unregisterListener(EventListener* listener)
{
	dwAssert(listener != NULL, "Invalid parameter: listener == NULL");
	dwAssert(std::find(_listeners.begin(), _listeners.end(), listener) != _listeners.end(), "Invalid parameter: listener is not registered");

	_listeners.erase(std::find(_listeners.begin(), _listeners.end(), listener));

	EventArg arg;

	listener->onUnregistered(this, arg);
}

//------------------------------------------------------------------------------
// Commands
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void Window::show()
{
	::ShowWindow(_hWnd, SW_NORMAL);
}

//------------------------------------------------------------------------------
void Window::hide()
{
	::ShowWindow(_hWnd, SW_HIDE);
}

//------------------------------------------------------------------------------
// Setup
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void Window::setTitle(const char* title)
{
	dwAssert(strlen(title) < TITLE_MAX_LENGTH, "Window's title is too long");

	strncpy_s(_title, title, TITLE_MAX_LENGTH);
	_title[TITLE_MAX_LENGTH - 1] = 0;

	::SetWindowText(_hWnd, _title);
}

//------------------------------------------------------------------------------
// State
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
const char* Window::getName() const
{
	return _name;
}

//------------------------------------------------------------------------------
uint32 Window::getWidth() const
{
	return _setup.width;
}

//------------------------------------------------------------------------------
uint32 Window::getHeight() const
{
	return _setup.height;
}

//------------------------------------------------------------------------------
uint32 Window::getBitDepth() const
{
	return _setup.bitDepth;
}

//------------------------------------------------------------------------------
bool Window::isActive() const
{
	return _isActive;
}

//------------------------------------------------------------------------------
HWND Window::getHandle() const
{
	return _hWnd;
}

//------------------------------------------------------------------------------
HDC Window::getDeviceContext() const
{
	return ::GetDC(_hWnd);
}

//------------------------------------------------------------------------------
// Callbacks
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void Window::onPaint()
{
	PaintEventArg arg;

	for (ListenersCollection::iterator it = _listeners.begin(); it != _listeners.end(); ++it)
	{
		(*it)->onPaint(this, arg);
	}
}

//------------------------------------------------------------------------------
void Window::onActivate()
{
	EventArg arg;

	for (ListenersCollection::iterator it = _listeners.begin(); it != _listeners.end(); ++it)
	{
		(*it)->onActivate(this, arg);
	}

	_isActive = true;

	dwLogDebug("(%s)::onActivate\n", this->getName());
}

//------------------------------------------------------------------------------
void Window::onDeactivate()
{
	EventArg arg;

	for (ListenersCollection::iterator it = _listeners.begin(); it != _listeners.end(); ++it)
	{
		(*it)->onDeactivate(this, arg);
	}

	_isActive = false;

	dwLogDebug("(%s)::onDeactivate\n", this->getName());
}

//------------------------------------------------------------------------------
void Window::onMove(int32 left, int32 top)
{
	MoveEventArg arg(_setup.left, _setup.top, left, top);

	for (ListenersCollection::iterator it = _listeners.begin(); it != _listeners.end(); ++it)
	{
		(*it)->onMove(this, arg);
	}

	_setup.left = left;
	_setup.top = top;
}

//------------------------------------------------------------------------------
void Window::onSize(uint32 width, uint32 height)
{
	SizeEventArg arg(_setup.width, _setup.height, width, height);

	for (ListenersCollection::iterator it = _listeners.begin(); it != _listeners.end(); ++it)
	{
		(*it)->onSize(this, arg);
	}

	_setup.width = width;
	_setup.height = height;
}

//------------------------------------------------------------------------------
void Window::onKeyDown(int32 key)
{
	KeyEventArg arg(key);

	for (ListenersCollection::iterator it = _listeners.begin(); it != _listeners.end(); ++it)
	{
		(*it)->onKeyDown(this, arg);
	}
}

//------------------------------------------------------------------------------
void Window::onKeyUp(int32 key)
{
	KeyEventArg arg(key);

	for (ListenersCollection::iterator it = _listeners.begin(); it != _listeners.end(); ++it)
	{
		(*it)->onKeyUp(this, arg);
	}
}

//------------------------------------------------------------------------------
void Window::onClose()
{
	EventArg arg;

	for (ListenersCollection::iterator it = _listeners.begin(); it != _listeners.end(); ++it)
	{
		(*it)->onClose(this, arg);
	}
}

//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------

/*static*/ const char Window::CLASS_NAME_SUFFIX[CLASS_NAME_SUFFIX_LENGTH] = "_class";

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void Window::create()
{
	WNDCLASSEX windowClass;

	windowClass.cbSize        = sizeof(WNDCLASSEX);
	windowClass.style         = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
	windowClass.lpfnWndProc   = WindowsManager::windowProc;
	windowClass.cbClsExtra    = 0;
	windowClass.cbWndExtra    = 0;
	windowClass.hInstance     = _hInstance;
	windowClass.hIcon         = ::LoadIcon(_hInstance, IDI_WINLOGO);
	windowClass.hIconSm       = ::LoadIcon(_hInstance, IDI_WINLOGO);
	windowClass.hCursor       = ::LoadCursor(NULL, IDC_ARROW);
	windowClass.hbrBackground = NULL;
	windowClass.lpszMenuName  = NULL;
	windowClass.lpszClassName = _className;

	if (!::RegisterClassEx(&windowClass))
	{
		dwFail("Failed to register window's class");
		return ;
	}

	if (_setup.fullScreen)
	{
		DEVMODE dmScreenSettings;

		memset(&dmScreenSettings, 0, sizeof (dmScreenSettings));
		dmScreenSettings.dmSize = sizeof (dmScreenSettings);
		dmScreenSettings.dmPelsWidth = _setup.width;
		dmScreenSettings.dmPelsHeight = _setup.height;
		dmScreenSettings.dmBitsPerPel = _setup.bitDepth;
		dmScreenSettings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;

		if (::ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
		{
			dwFail("Can not change display mode to fullscreen. Initializing in windowed mode");
			_setup.fullScreen = false;
		}
	}

	DWORD dwExStyle = WS_EX_APPWINDOW | (_setup.fullScreen ? 0 : WS_EX_WINDOWEDGE);
	DWORD dwStyle = _setup.fullScreen ? WS_POPUP : WS_OVERLAPPEDWINDOW;
	RECT wndRect;

	wndRect.left = (long)_setup.left;
	wndRect.right = (long)_setup.width;
	wndRect.top = (long)_setup.top;
	wndRect.bottom = (long)_setup.height;

	if (!::AdjustWindowRectEx(&wndRect, dwStyle, false, dwExStyle))
	{
		dwFail("Failed to adjust window's geomerty");
		return ;
	}

	_hWnd = ::CreateWindowEx(dwExStyle, _className, _name,
		WS_CLIPSIBLINGS | WS_CLIPCHILDREN | dwStyle, _setup.left, _setup.top, _setup.width, _setup.height, NULL, NULL, _hInstance, NULL);

	dwAssert(_hWnd != NULL, "Window was not created");
}

//------------------------------------------------------------------------------
void Window::destroy()
{
	if (_setup.fullScreen)
	{
		if (::ChangeDisplaySettings(NULL, 0) != DISP_CHANGE_SUCCESSFUL)
		{
			dwFail("Can not change display mode back to normal. Initializing in windowed mode");
		}
	}

	::DestroyWindow(_hWnd);
	::UnregisterClass(_className, _hInstance);

	_hWnd = NULL;
	_hInstance = NULL;
}

//------------------------------------------------------------------------------
// Construction & destruction
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Window::Setup::Setup()
:	left(0)
,	top(0)
,	width(0)
,	height(0)
,	bitDepth(0)
,	fullScreen(false)
{

}

//------------------------------------------------------------------------------
Window::Setup::Setup(int32 pLeft, int32 pTop, uint32 pWidth, uint32 pHeight, uint32 pBitDepth /* = 32 */, bool pFullScreen /* = false */)
:	left(pLeft)
,	top(pTop)
,	width(pWidth)
,	height(pHeight)
,	bitDepth(pBitDepth)
,	fullScreen(pFullScreen)
{
}

	} // namespace system
} // namespace dw
